package com.yuelao.yuelao_backend.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.GrantedAuthority;
import com.yuelao.yuelao_backend.entity.User;
import com.yuelao.yuelao_backend.service.security.CustomUserDetails;

import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class JwtUtil {

    private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);

    @Value("${yuelao.jwt.secret}")
    private String secret;

    @Value("${yuelao.jwt.expiration}")
    private Long expiration;

    @Value("${yuelao.jwt.refresh-expiration}")
    private Long refreshExpiration;

    private SecretKey signingKey;
    
    // 确保在整个应用生命周期中使用相同的密钥
    private SecretKey getSigningKey() {
        if (signingKey != null) {
            return signingKey;
        }
        
        try {
            // 确保密钥至少有256位(32字节)
            byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
            
            // 使用HMAC-SHA256算法要求的最小密钥长度
            if (keyBytes.length < 32) {
                // 密钥太短，使用标准方法生成安全密钥，但要确保每次都使用相同的密钥
                // 通过哈希原始密钥来生成一个确定性的密钥
                keyBytes = java.security.MessageDigest.getInstance("SHA-256").digest(keyBytes);
            }
            
            // 如果密钥超过64字节，进行哈希处理以确保合适长度
            if (keyBytes.length > 64) {
                keyBytes = java.security.MessageDigest.getInstance("SHA-256").digest(keyBytes);
            }
            
            signingKey = Keys.hmacShaKeyFor(keyBytes);
            return signingKey;
        } catch (Exception e) {
            // 如果出现任何异常，回退到生成标准安全密钥
            signingKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
            return signingKey;
        }
    }

    public String generateAccessToken(UserDetails userDetails) {
        Map<String, Object> claims = buildClaims(userDetails);
        return generateToken(claims, userDetails.getUsername(), expiration);
    }

    public String generateRefreshToken(UserDetails userDetails) {
        Map<String, Object> claims = buildClaims(userDetails);
        return generateToken(claims, userDetails.getUsername(), refreshExpiration);
    }

    private String generateToken(Map<String, Object> claims, String subject, Long expirationTime) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expirationTime * 1000))
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    private Claims extractAllClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    private Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    public Boolean isTokenValid(String token) {
        try {
            return !isTokenExpired(token);
        } catch (Exception e) {
            logger.warn("JWT token validation failed for token: {}", token, e);
            return false;
        }
    }

    /**
     * 从expired token中提取用户名（用于refresh token场景）
     * @param token 可能是expired的token
     * @return 用户名
     * @throws Exception token无法解析时抛出异常
     */
    public String extractUsernameFromExpiredToken(String token) {
        try {
            return extractClaim(token, Claims::getSubject);
        } catch (Exception e) {
            // 如果是expired token，尝试手动解析
            try {
                Claims claims = Jwts.parserBuilder()
                    .setSigningKey(getSigningKey())
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
                return claims.getSubject();
            } catch (Exception ex) {
                logger.error("无法从token中提取用户名", ex);
                throw new RuntimeException("无法解析token", ex);
            }
        }
    }

    public Long extractUserId(String token) {
        Object v = extractClaim(token, claims -> claims.get("uid"));
        if (v instanceof Number) {
            return ((Number) v).longValue();
        }
        return null;
    }

    public String extractPhoneClaim(String token) {
        Object v = extractClaim(token, claims -> claims.get("ph"));
        return v != null ? String.valueOf(v) : null;
    }

    public String extractNickname(String token) {
        Object v = extractClaim(token, claims -> claims.get("nn"));
        return v != null ? String.valueOf(v) : null;
    }

    public Integer extractStatus(String token) {
        Object v = extractClaim(token, claims -> claims.get("st"));
        if (v instanceof Number) {
            return ((Number) v).intValue();
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public List<String> extractRoles(String token) {
        Object v = extractClaim(token, claims -> claims.get("roles"));
        if (v instanceof List) {
            return (List<String>) v;
        }
        return java.util.Collections.emptyList();
    }

    private Map<String, Object> buildClaims(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        if (userDetails instanceof CustomUserDetails) {
            CustomUserDetails cud = (CustomUserDetails) userDetails;
            User u = cud.getUser();
            if (u != null) {
                claims.put("uid", u.getId());
                claims.put("ph", u.getPhone());
                claims.put("nn", u.getNickname());
                claims.put("st", u.getStatus());
            }
        }
        List<String> roles = userDetails.getAuthorities()
                .stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());
        claims.put("roles", roles);
        return claims;
    }
}